package chapterSystem.server;

import chapterSystem.client.ChapterSystemService;
import chapterSystem.client.entities.Pair;
import chapterSystem.client.entities.UnitInfo;
import chapterSystem.client.model.entitiesInt.*;
import chapterSystem.client.model.enums.ActivityType;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;


public class ChapterSystemServiceImpl extends RemoteServiceServlet implements ChapterSystemService {
    final static private String SALT = "knr3t7yrtbqte";
    final static long COOKIE_REFRESH_TIME = 1000 * 60 * 20; //20 min

    final static private ConcurrentHashMap<CookieKey, Integer> cookies = new ConcurrentHashMap<CookieKey, Integer>();

    static {
        java.util.Timer timer = new java.util.Timer();
        TimerTask task = new TimerTask() {
            public void run() {
                for (CookieKey cookie : cookies.keySet()) {
                    if (cookie.timeCount >= 3) {// cookie expires in 1+ hour
                        cookies.remove(cookie);
                    } else {
                        cookie.timeCount++;
                    }
                }
            }
        };
        timer.schedule(task, COOKIE_REFRESH_TIME);
    }

    private ModelConnector model = ModelConnector.getInstance();   //TODO null check + error handling

    @Override
    public Brother login(String username, String pass) {
        try {
            Brother brother;
            if ((brother = model.login(username, pass)) != null) {
                String cookie = calculateCookie();
                cookies.put(new CookieKey(cookie, 0), brother.getId());
                brother.setCookie(cookie);
                return brother;
            }

            return null;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String editBattleOrderText(Order order) {
        try {
            return model.editBattleOrderText(order);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    @Override
    public Brother checkCookie(String cookie) {
        if (cookie != null && cookie.equals(calculateCookie())) {
            Integer id = cookies.get(new CookieKey(cookie, 0));
            if (id == null) {
                return null;
            }

            try {
                return model.getById(id);
            } catch (SQLException e) {
                return null;
            }
        }

        return null;
    }

    @Override
    public List<Report> getReportsById(int id) {
        try {
            return model.getReportsById(id);
        } catch (SQLException e) {
            return null;
        }
    }

    @Override
    public Pair<List<Order>, List<Order>> getOrdersById(int id) {
        try {
            final List<Order> ordersByAuthor = model.getOrdersByAuthor(id);
            final List<Order> ordersByReceiver = model.getOrdersByReceiver(id);

            return new Pair<List<Order>, List<Order>>(ordersByAuthor, ordersByReceiver);
        } catch (SQLException e) {
            return null;
        }
    }

    @Override
    public List<ChapterEvent> getEvents(int eventCount, java.sql.Date fromDate, java.sql.Date toDate) {
        try {
            return model.getEvents(eventCount, fromDate, toDate);
        } catch (SQLException e) {
            return null;
        }
    }

    @Override
    public String createNewBrother(Brother brother, String password) {
        try {
            return model.addNewBrother(brother, password == null ? null : calculateMD5(password));
        } catch (SQLException e) {
            return "Error: " + e.getMessage();
        }
    }

    @Override
    public String editBrother(final Brother brother) {
        try {
            return model.editBrother(brother);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    @Override
    public UnitInfo getUnitInfo(final int userId) {
        System.out.println("Start getting unit...");
        try {
            return model.getUnitInfo(userId);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public Map<String, Unit> getAllUnits() {
        try {
            return model.getAllUnits();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String submitOrderExecution(Order order, String briefReport) {
        try {
            return model.createOrderExecutionReport(order, briefReport);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    public String editExecutionReport(Order order) {
        try {
            return model.editExecutionReport(order);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    @Override
    public Brother getBrotherByLogin(final String login) {
        try {
            return model.getBrother(login);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String createBattleOrder(Order order) {
        try {
            return model.createBattleOrder(order);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    public List<String> getBrotherLog(int brotherId) {
        try {
            return model.getBrotherLog(brotherId);
        } catch (SQLException e) {
            System.out.println("fail to get log");
            return new ArrayList<String>();
        }
    }

    @Override
    public String addLog(final int brotherId, final ActivityType type, final String message) {
        try {
            model.addLog(brotherId, type.getId(), message);
            return "Success";
        } catch (SQLException e) {
            e.printStackTrace();
            return "Failed";
        }
    }

    public String submitReport(int brotherId, int eventId, String reportText) {
        try {
            return model.submitReport(brotherId, eventId, reportText);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    public List<Report> getReportsByEvent(int eventId, int brotherRank) {
        try {
            return model.getReportsByEvent(eventId, brotherRank);
        } catch (SQLException e) {
            System.out.println(e.getMessage());
            return new ArrayList<Report>();
        }
    }

    public String editReport(Report report) {
        try {
            return model.editReport(report);
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    public String createEvent(ChapterEvent event) {
        try {
            return model.createEvent(event);
        } catch (SQLException e) {
            return e.getMessage();
        }
    }

    private String calculateCookie() {
        final String address = getThreadLocalRequest().getRemoteAddr();
        return calculateMD5(SALT + address);
    }

    private String calculateMD5(String str) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            return new BigInteger(1, messageDigest.digest(str.getBytes())).toString(16);
        } catch (NoSuchAlgorithmException ignored) {
            //never happens
        }

        return null;
    }


    static private class CookieKey {
        private CookieKey(String key, int timeCount) {
            this.key = key;
            this.timeCount = timeCount;
        }

        @Override
        public boolean equals(Object obj) {//ugly, but simple
            return (obj instanceof CookieKey && ((CookieKey) obj).key.equals(this.key)) ||
                    (obj instanceof String && obj.equals(key));
        }

        @Override
        public int hashCode() {
            return key.hashCode();
        }

        String key;
        int timeCount;
    }
}